#ifndef __M_ROOM_H__
#define __M_ROOM_H__
#include "util.hpp"
#include "logger.hpp"
#include "online.hpp"
#include "db.hpp"
#include <vector>

/*棋盘的大小为15*15*/
#define BOARD_ROW 15
#define BOARD_COL 15
/*1代表白棋玩家，2代表黑棋玩家*/
#define CHESS_WHITE 1
#define CHESS_BLACK 2
/*定义房间状态：游戏开始，游戏结束*/
typedef enum{GAME_START, GAME_OVER}room_status;
/*房间类中，一个房间，需要有的信息是：房间的id，房间的状态，房间玩家数量，玩家的id（黑白棋），在线用户管理，数据模块管理*/
class room
{   
private:
    uint64_t _room_id;/*房间id*/
    room_status _statu;/*房间状态*/
    uint64_t _player_count;/*玩家数量*/
    uint64_t _white_id;/*白棋玩家的id*/
    uint64_t _black_id;/*黑棋玩家的id*/
    user_table* _tb_user;/*数据模块管理*/
    online_manager* _online_user;/*在线用户管理*/
    std::vector<std::vector<int>> _board;/*棋盘*/
private:
    /*判断是否五星连珠的方法，参数为：当前棋子的坐标，棋子后续的偏移量，也就是方向，然后是棋子的颜色*/
    /**/
    bool five(int row,int col,int row_off,int col_off,int color)
    {
        int cnt = 1;//一开始，连续相同颜色棋子的个数只有一个，也就是当前的棋子
        int search_row = row + row_off;/*当前坐标加上坐标的偏移量，也就是向左或向右或正斜或反斜*/
        int search_col = col + col_off;/*当前坐标加上坐标的偏移量，也就是向左或向右或正斜或反斜*/
        /*首先，判断一个方向的*/
        while(search_row>=0 && search_row<BOARD_ROW && 
              search_col>=0 && search_col<BOARD_COL 
            && _board[search_row][search_col]==color)
        {
            /*同色棋子加1*/
            cnt++;
            search_row += row_off;
            search_col += col_off;
        }
        /*判断反方向*/
        search_row = row - row_off;
        search_col = col - col_off;
        while(search_row >= 0 && search_row < BOARD_ROW &&
            search_col >= 0 && search_col < BOARD_COL &&
            _board[search_row][search_col] == color) 
        {
            //同色棋子数量++
            cnt++;
            //检索位置继续向后偏移
            search_row -= row_off;
            search_col -= col_off;
        }
        return (cnt>=5);
    }

    uint64_t check_win(int row, int col, int color)
    {
        // 从下棋位置的四个不同方向上检测是否出现了5个及以上相同颜色的棋子（横行，纵列，正斜，反斜）
        if (five(row, col, 0, 1, color) || 
            five(row, col, 1, 0, color) ||
            five(row, col, -1, 1, color)||
            five(row, col, -1, -1, color)) 
        {
            //任意一个方向上出现了true也就是五星连珠，则设置返回值
            return color == CHESS_WHITE ? _white_id : _black_id;
        }
        return 0;
    }
public:
    room(uint64_t room_id, user_table *tb_user, online_manager *online_user)
        :_room_id(room_id),_tb_user(tb_user),_online_user(online_user)
        ,_statu(GAME_START),_player_count(0),_board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
    {
        DLOG("%lu 房间创建成功!!", _room_id);
    }
    ~room()
    {
        DLOG("%lu 房间销毁成功!!", _room_id);
    }

    /*获取房间id*/
    uint64_t get_rid(){ return _room_id; }
    uint64_t get_white_user(){ return _white_id; }/*获取白棋玩家id*/
    uint64_t get_black_user(){ return _black_id;}/*获取黑棋玩家id*/
    room_status statu(){ return _statu; }/*获取房间状态*/
    int player_count(){ return _player_count; }/*获取玩家数量*/
    void add_white_user(uint64_t uid){ _white_id = uid; _player_count++; }/*添加白棋玩家到房间中*/
    void add_black_user(uint64_t uid){ _black_id = uid; _player_count++; }/*添加黑棋玩家到房间中*/

    /*处理下棋动作*/
    Json::Value handle_chess(Json::Value& req)
    {
        /*在下棋动作，分几种情况进行处理
        1.一方掉线，那么另一方不战而胜
        2.在下棋的时候，棋子的位置已经被占用，提示玩家重新选择下棋位置
        3.一方下完棋子后，判断是否五星连珠，如果是，则胜利*/

        /*首先，创建一个用于响应的Json*/
        Json::Value resp_json = req;/*直接拷贝req的数据，因为req的数据中已经有了下棋玩家的一些数据*/
        int chess_row = req["row"].asInt();//当前下棋的位置
        int chess_col = req["col"].asInt();//当前下棋的位置
        uint64_t cur_uid = req["uid"].asUInt64();//当前下棋的玩家
        /*进行判断，双方是否都在线，如果一方掉线，那么另一方不战而胜*/
        if(_online_user->is_in_game_room(_white_id)==false)/*白棋玩家掉线*/
        {
            resp_json["result"] = true;
            resp_json["reason"] = "对方掉线，不战而胜！";
            resp_json["winner"] = (Json::UInt64)_black_id;
            return resp_json;
        }
        if(_online_user->is_in_game_room(_black_id)==false)/*黑棋玩家掉线*/
        {
            resp_json["result"] = true;
            resp_json["reason"] = "对方掉线，不战而胜！";
            resp_json["winner"] = (Json::UInt64)_white_id;
            return resp_json;
        }
        /*双方在线*/
        /*获取走棋的位置，判断当前走棋是否合理（位置是否已经被占用）*/

        if(_board[chess_row][chess_col]!=0)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "当前位置已经有了其他棋子！";
            return resp_json;
        }
        /*位置合理，下棋,*/
        int cur_color = cur_uid == _white_id ? CHESS_WHITE : CHESS_BLACK;//看看当前下棋的玩家是黑棋还是白棋，需要对应起来
        _board[chess_row][chess_col] = cur_color;//下棋
        /*下完棋后需要判断是否有玩家胜利（从当前走棋位置开始判断是否存在五星连珠）*/
        uint64_t winner_id = check_win(chess_row,chess_col,cur_color);
        if (winner_id != 0)/*如果返回来不为0，说明游戏结束*/
        {
            resp_json["reason"] = "五星连珠，战无敌！";
        }
        resp_json["result"] = true;
        resp_json["winner"] = (Json::UInt64)winner_id;
        return resp_json;
    }

    /*处理聊天动作*/
    Json::Value handle_chat(Json::Value& req)
    {
        /*处理聊天动作很简单，就是查看聊天消息中有没有敏感词*/
        Json::Value resp_json = req;
        /*获取其中的信息*/
        std::string msg = req["message"].asString();
        size_t pos = msg.find("垃圾");
        if(pos != std::string::npos)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "消息中包含了敏感词,不能发送";
            return resp_json;
        }
        resp_json["result"] = true;
        return resp_json;
    }

    /*处理玩家退出房间动作*/
    void handle_exit(uint64_t uid)
    {
        Json::Value resp_json;
        /*对于玩家退出房间，有两种情况*/
        /*游戏进行中时退出房间,这种情况下，对手不战而胜*/
        if(_statu==GAME_START)
        {
            uint64_t winner_id = (Json::UInt64)(uid == _white_id?_black_id:_white_id);//找到胜利玩家的id
            resp_json["optype"] = "put_chess";
            resp_json["result"] = true;
            resp_json["reason"] = "对方掉线，不战而胜！";
            resp_json["room_id"] = (Json::UInt64)_room_id;
            resp_json["uid"] = (Json::UInt64)uid;
            resp_json["row"] = -1;
            resp_json["col"] = -1;
            resp_json["winner"] = (Json::UInt64)winner_id;
            uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;//找到失败玩家的id
            _tb_user->win(winner_id);/*从数据管理模块中处理胜利玩家的数据*/
            _tb_user->loser(loser_id);/*从数据管理模块中处理失败玩家的数据*/
            _statu = GAME_OVER;
            broadcast(resp_json);//广播
        }

        /*游戏结束后正常退出房间*/
        _player_count--;
        return;
    }
    /*总的请求处理函数，在函数内部，区分请求类型，根据不同的请求调用不同的处理函数，得到响应进行广播*/
    void handle_request(Json::Value& req)
    {
        Json::Value resp_json;
        /*1.校验房间号是否匹配*/
        uint64_t room_id = req["room_id"].asUInt64();
        if(_room_id!=room_id)
        {
            resp_json["optype"] = req["optype"].asString();
            resp_json["result"] = false;
            resp_json["reason"] = "房间号不匹配！";
            return broadcast(resp_json);
        }
        //2. 根据不同的请求类型调用不同的处理函数
        if(req["optype"].asString()=="put_chess")
        {
            resp_json = handle_chess(req);
            if (resp_json["winner"].asUInt64() != 0) 
            {
                uint64_t winner_id = resp_json["winner"].asUInt64();
                uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win(winner_id);
                _tb_user->loser(loser_id);
                _statu = GAME_OVER;
            }
        }
        else if(req["optype"].asString()=="chat")
        {
            resp_json=handle_chat(req);
        }
        else
        {
            resp_json["optype"] = req["optype"].asString();
            resp_json["result"] = false;
            resp_json["reason"] = "未知请求类型";
        }
        std::string body;
        json_util::serialize(resp_json, body);
        DLOG("房间-广播动作: %s", body.c_str());
        return broadcast(resp_json);
    }

    /*将指定的信息广播给房间中所有玩家*/
    void broadcast(Json::Value& rsp)
    {
        /*进行序列化，然后获取房间中所有用户的通信连接，然后将消息发送出去*/
        std::string body;
        json_util::serialize(rsp,body);
        wsserver_t::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);
        if(wconn.get()!=nullptr)
        {
            wconn->send(body);
        }
        else 
        {
            DLOG("房间-白棋玩家连接获取失败");
        }

        wsserver_t::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);
        if(bconn.get()!=nullptr)
        {
            bconn->send(body);
        }
        else 
        {
            DLOG("房间-黑棋玩家连接获取失败");
        }
        return;
    }

};

using room_ptr = std::shared_ptr<room>;
class room_manager
{
private:
    uint64_t _next_rid;/*房间编号计数器*/
    std::unordered_map<uint64_t,room_ptr> _rooms;/*将房间编号与智能指针建立映射关系*/
    std::unordered_map<uint64_t,uint64_t> _users;/*将房间编号与用户id建立映射关系*/
    std::mutex _mutex;
    online_manager* _online_user;
    user_table* _tb_user;
public:
    /*初始化房间ID计数器*/
    room_manager(user_table *ut, online_manager *om):
        _next_rid(1), _tb_user(ut), _online_user(om) 
    {
        DLOG("房间管理模块初始化完毕！");
    }
    ~room_manager() { DLOG("房间管理模块即将销毁！"); }
    /*创建房间，并将两个玩家添加到房间中去*/
    room_ptr create_room(uint64_t uid1,uint64_t uid2)
    {
        //两个用户在游戏大厅中进行对战匹配，匹配成功后创建房间
        //1. 校验两个用户是否都还在游戏大厅中，只有都在才需要创建房间。
        if(_online_user->is_in_game_hall(uid1)==false)
        {
            DLOG("用户：%lu 不在大厅中，创建房间失败!", uid1);
            return room_ptr();
        }

        if(_online_user->is_in_game_hall(uid2)==false)
        {
            DLOG("用户：%lu 不在大厅中，创建房间失败!", uid2);
            return room_ptr();
        }

        //2. 创建房间，将用户信息添加到房间中
        std::unique_lock<std::mutex> lock(_mutex);
        //创建一个执行房间的智能指针
        room_ptr rp(new room(_next_rid, _tb_user, _online_user));
        rp->add_white_user(uid1);
        rp->add_black_user(uid2);
        //3.将房间管理起来
        _rooms.insert(std::make_pair(_next_rid,rp));
        _users.insert(std::make_pair(uid1,_next_rid));
        _users.insert(std::make_pair(uid2,_next_rid));
        _next_rid++;
        //4.返回管理房间的智能指针
        return rp;
    }

    /*通过房间id获取房间信息*/
    room_ptr get_room_by_rid(uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _rooms.find(rid);
        if(it==_rooms.end())
        {
            return room_ptr();
        }
        return it->second;
    }

    /*通过用户id获取房间信息*/
    room_ptr get_room_by_uid(uint64_t uid) 
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto uit = _users.find(uid);
        if(uit==_users.end())
        {
            return room_ptr();
        }
        uint64_t rid = uit->second;
        auto rit = _rooms.find(rid);
        if(rit==_rooms.end())
        {
            return room_ptr();
        }
        return rit->second;
    }

    /*通过房间ID销毁房间*/
    void remove_room(uint64_t rid)
    {
        /*销毁一个房间，需要先将房间中的玩家全部移除，最后再将房间的管理移除出去，即销毁成功*/
        /*通过房间id，获取房间的指针*/
        room_ptr rp = get_room_by_rid(rid);
        if (rp.get() == nullptr) 
        {
            return;
        }
        /*通过房间指针，获取到房间内玩家的id*/
        uint64_t uid1 = rp->get_white_user();
        uint64_t uid2 = rp->get_black_user();
        /*通过玩家的id，移除房间管理中的用户信息*/
        std::unique_lock<std::mutex> lock(_mutex);
        _users.erase(uid1);
        _users.erase(uid2);
        /*最后通过房间id，移除出去*/
        _rooms.erase(rid);
    }
    /*删除房间中指定用户，如果房间中没有用户了，则销毁房间，用户连接断开时被调用*/
    void remove_room_user(uint64_t uid)
    {
        /*通过用户id，找到对应的房间*/
        room_ptr rp = get_room_by_uid(uid);
        if (rp.get() == nullptr) 
        {
            return;
        }
        /*通过处理玩家退出房间的动作，来移除该名玩家*/
        rp->handle_exit(uid);
        //房间中没有玩家了，则销毁房间
        if (rp->player_count() == 0) 
        {
            remove_room(rp->get_rid());
        }
        return ;
    }
};


#endif